﻿with Ada.Numerics.Elementary_Functions;
use Ada.Numerics.Elementary_Functions;
with Ada.Text_Io; use Ada.Text_Io;
--  with Ada.long_float_Text_Io; use Ada.long_float_Text_Io;
with Ada.Numerics.Generic_Elementary_Functions;
with Ada.Numerics.Long_Elementary_Functions; use Ada.Numerics.Long_Elementary_Functions;
with Ada.Integer_Text_Io; use Ada.Integer_Text_Io;

with Vecteurs; use Vecteurs;
with Impacts_Listes; use Impacts_Listes;

package body Moteur_Physique is

   Friction_Mur : Long_Float := 1.0;

   procedure Update_Position (Objet : in out Bille_Type;  Intervalle_Temps : Long_Float) is
   begin
      Objet.Pos := Objet.Pos + Objet.Vitesse * Intervalle_Temps;
   end;

   procedure Update_Pos (Scene : in out Scene_Type; Dt : in Long_Float) is
   begin
      for I in 1 .. Scene.Get_Nb_Objets loop
         declare
            Obj_Modifie : Bille_Type := Scene.get_objet (I);
         begin
            if not Obj_Modifie.Fixe then

               Update_Position (Obj_Modifie, Dt);
               Scene.Remplacer_Objet(I, Obj_Modifie);
            end if;

         end;
      end loop;
   end Update_Pos;


   procedure Collision (Scene : in out Scene_Type; Id1, Id2 : in Integer) is
      Obj1 : Bille_Type := Scene.get_objet (Id1);
      Obj2 : Bille_Type := Scene.get_objet (Id2);
   begin
      Choc (Obj1, Obj2);
      Scene.Remplacer_Objet (Id1, Obj1);
      Scene.Remplacer_Objet (Id2, Obj2);
   end Collision;

   -----------------------------------------------------------------------------------------------------------------------------

   procedure Remplir_Impact_Murs (Scene : Scene_Type; Id : Natural ; Liste : in out Impacts_Liste_Type; Delta_Temps : Long_Float) is
      O     : Bille_Type := Scene.get_objet (Id);
      Imp   : T_Impact;
      Dt    : Long_Float;
   begin
      Imp.Id1 := Id;
      --haut
      if O.Vitesse.Y > 0.0 and then O.Pos.Y <= Get_Height (Scene) - O.Rayon then
         Dt := (Get_Height (Scene) - O.Rayon - O.Pos.Y) / O.Vitesse.Y;
         if Dt < Delta_Temps then
            Imp.Dt := Dt;
            Imp.Genre := Mur_Haut;
            Liste.Append (Imp);
         end if;
      end if;

      --bas
      if O.Vitesse.Y < 0.0 and then O.Pos.Y >= O.Rayon then
         Dt := (O.Rayon - O.Pos.Y) / (O.Vitesse.Y);
         if Dt < Delta_Temps then
            Imp.Dt := Dt;
            Imp.Genre := Mur_Bas;
            Liste.Append (Imp);
         end if;
      end if;

      --gauche
      if O.Vitesse.X < 0.0 and then O.Pos.X >= O.Rayon then
         Dt := (O.Rayon - O.Pos.X) / O.Vitesse.X;
         if Dt < Delta_Temps  then
            Imp.Dt := Dt;
            Imp.Genre := Mur_Gauche;
            Liste.Append (Imp);
         end if;
      end if;

      --droite
      if O.Vitesse.X > 0.0 and then O.Pos.X < Get_Width (Scene) - O.Rayon  then
         Dt := (Get_Width (Scene) - O.Rayon - O.Pos.X) / O.Vitesse.X;
         if Dt < Delta_Temps then
            Imp.Dt := Dt;
            Imp.Genre := Mur_Droite;
            Liste.Append (Imp);
         end if;
      end if;
   end Remplir_Impact_Murs;

   function Impact_Futurs (Scene : Scene_Type; Id : Positive; Delta_Temps : Long_Float; Except : Natural := 0) return Impacts_Liste_Type is
      Imp     : T_Impact;
      Dt      : Long_Float;
      Liste   : Impacts_Liste_Type;
      O       : Bille_Type := Scene.get_objet (Id);
      Grille  : T_Grille renames Scene.Get_Grille;

      Cursor  : Id_Ordered_Sets.Cursor; Obj2 : Bille_Type;
      use type Id_Ordered_Sets.Cursor;

   begin
      Remplir_Impact_Murs (Scene, Id, Liste, Delta_Temps);

      for X in O.Rect.Xmin .. O.Rect.Xmax loop
         for Y in O.Rect.Ymin .. O.Rect.Ymax loop
            Cursor := Grille (X, Y).First;

            while Cursor /= Id_Ordered_Sets.No_Element loop
               Obj2 := Scene.get_objet (Id_Ordered_Sets.Element (Cursor));
               if Scalaire ((Obj2.Vitesse - O.Vitesse), (Obj2.Pos - O.Pos)) < 0.0 then
                  Dt := Temps_Avant_Impact (O, Obj2);
                  if Dt < Delta_Temps then
                     Imp.Dt := Dt;
                     Imp.Id1 := Id;
                     Imp.Id2 := Id_Ordered_Sets.Element (Cursor);
                     Imp.Genre := Bille;
                     Liste.Append (Imp);
                  end if;
               end if;


               Cursor := Id_Ordered_Sets.Next (Cursor);
            end loop;

         end loop;
      end loop;

      return Liste;
   end Impact_Futurs;


   procedure Remplir_Premiers_Impacts (Scene : Scene_Type; Delta_Temps : Long_Float; Liste : out Impacts_Liste_Type) is
      Imp             : T_Impact;
      Dt              : Long_Float;
      Grille          : T_Grille renames Scene.Get_Grille;
      Cursor, Cursor2 : Id_Ordered_Sets.Cursor;
      Obj1, Obj2      : Bille_Type;
      use type Id_Ordered_Sets.Cursor;
   begin
      for Id in 1 .. Scene.Get_Nb_Objets loop
         Remplir_Impact_Murs (Scene, Id, Liste, Delta_Temps);
      end loop;

      -- on parcourt la grille suivant x et y
      for X in 0 .. Scene.Get_Nb_Subdiv - 1 loop
         for Y in 0 .. Scene.Get_Nb_Subdiv - 1 loop
            Cursor := Grille (X, Y).First;

            -- on parcourt tous les impacts contenus dans la case grille(X,Y)
            while Cursor /= Id_Ordered_Sets.No_Element loop
               Cursor2 := Id_Ordered_Sets.Next (Cursor);
               Obj1 := Scene.get_objet (Id_Ordered_Sets.Element (Cursor));


               while Cursor2 /= Id_Ordered_Sets.No_Element loop
                  Obj2 := Scene.get_objet (Id_Ordered_Sets.Element (Cursor2));

                  if Scalaire ((Obj2.Vitesse - Obj1.Vitesse), (Obj2.Pos - Obj1.Pos)) < 0.0 then

                     Dt := Temps_Avant_Impact (Obj1, Obj2);
                     if Dt < Delta_Temps then
                        Imp.Dt := Dt;
                        Imp.Id1 := Id_Ordered_Sets.Element (Cursor);
                        Imp.Id2 := Id_Ordered_Sets.Element (Cursor2);
                        Imp.Genre := Bille;
                        Liste.Append (Imp);
                     end if;
                  end if;


                  Cursor2 := Id_Ordered_Sets.Next (Cursor2);
               end loop;

               Cursor := Id_Ordered_Sets.Next (Cursor);
            end loop;

         end loop;
      end loop;
   end Remplir_Premiers_Impacts;

   procedure Appliquer_Acceleration (Scene : in out Scene_Type; Dt : Long_Float) is
      Obj : Bille_Type;
   begin
      for I in 1 .. Scene.Get_Nb_Objets loop
         Obj := Scene.get_objet (I);

         if not Obj.Fixe then
            Obj.Vitesse.X := Obj.Vitesse.X + Obj.Acceleration.X * Dt;
            Obj.Vitesse.Y := Obj.Vitesse.Y + Obj.Acceleration.Y * Dt;
            Scene.Remplacer_Objet (I, Obj);
         end if;

      end loop;
   end Appliquer_Acceleration;


   procedure Update (Scene : in out Scene_Type; Step : Long_Float := (1.0/60.0)) is
      Imp            : T_Impact;
      Liste          : Impacts_Liste_Type;
      Dt             : Long_Float := Step; -- pas en temps
   begin

      Appliquer_Acceleration (Scene, Dt);

      -------------------------------------------------------------
      -- on efface la grille préalablement remplie
      Effacer_Grille (Scene);
      -- et on la remplit correctement
      Remplir_Grille (Scene, Dt);
      -------------------------------------------------------------

      Remplir_Premiers_Impacts (Scene, Dt, Liste);

      while Dt > 0.0 loop
         Imp := Plus_Proche_Impact (Liste);
         if Imp.Genre /= Aucun and Integer (Liste.Length) /= 0 then
            Update_Pos (Scene, Imp.Dt);

            Dt := Dt - Imp.Dt;
            declare
               Obj : Bille_Type := Scene.get_objet (Imp.Id1);
            begin
               case Imp.Genre is
                  when Bille =>
                     Collision (Scene, Imp.Id1, Imp.Id2);
                     Supprimer_Et_Decrementer (Liste, Imp.Id1, Imp.Id2, Imp.Dt);

                     -----------------------------------------------------------------
                     Supprimer_Id_Grille (Scene, Imp.Id1);
                     Remplir_Grille (Scene, Imp.Id1, Dt);

                     Supprimer_Id_Grille (Scene, Imp.Id2);
                     Remplir_Grille (Scene, Imp.Id2, Dt);
                     ------------------------------------------------------------------

                     Liste.Append (Impact_Futurs (Scene, Imp.Id1, Dt, Imp.Id2));
                     Liste.Append (Impact_Futurs (Scene, Imp.Id2, Dt, Imp.Id1));

                  when Mur_Gauche | Mur_Droite =>
                     Obj.Vitesse.X := - Amortissement_Mur * Obj.Vitesse.X;
                     Obj.Vitesse.Y := Friction_Mur * Obj.Vitesse.Y;

                     Scene.Remplacer_Objet (Imp.Id1, Obj);

                     Supprimer_Et_Decrementer (Liste, Imp.Id1, Dt => Imp.Dt);

                     ------------------------------------------------------
                     Supprimer_Id_Grille (Scene, Imp.Id1);
                     Remplir_Grille (Scene, Imp.Id1, Dt);
                     -----------------------------------------------------
                     Liste.Append (Impact_Futurs (Scene, Imp.Id1, Dt));

                  when Mur_Haut | Mur_Bas =>
                     if Imp.Genre = Mur_Bas then
                        Obj.Pos.Y := Obj.Rayon;
                     end if;
                     Obj.Vitesse.Y := - Amortissement_Mur * Obj.Vitesse.Y;
                     Obj.Vitesse.X := Friction_Mur * Obj.Vitesse.X;

                     Scene.Remplacer_Objet (Imp.Id1, Obj);

                     Supprimer_Et_Decrementer (Liste, Imp.Id1, Dt => Imp.Dt);

                     ----------------------------------------------------
                     Supprimer_Id_Grille (Scene, Imp.Id1);
                     Remplir_Grille (Scene, Imp.Id1, Dt);
                     ----------------------------------------------------
                     Liste.Append (Impact_Futurs (Scene, Imp.Id1, Dt));

                  when others =>
                     null;
               end case;
            end;
         else
            Update_Pos (Scene, Dt);
            exit;
         end if;
      end loop;
   end Update;


   -- autre version -------------------------------------------------------------------


   function Time_Back (O1, O2 : Bille_Type) return Long_Float is
      T      : Long_Float := 0.0;
      Vx     : Long_Float := O2.Vitesse.X - O1.Vitesse.X;
      Vy     : Long_Float := O2.Vitesse.Y - O1.Vitesse.Y;
      Ox     : Long_Float := O2.Pos.X - O1.Pos.X;
      Oy     : Long_Float := O2.Pos.Y - O1.Pos.Y;
      R      : Long_Float := O2.Rayon + O1.Rayon;
      Soluce : Solution_Type;
   begin
      Nb_Calcul := Nb_Calcul + 1;
      Soluce := Solve (Vy ** 2 + Vx ** 2, 2.0 * (Ox * Vx + Oy * Vy), Ox ** 2 + Oy ** 2 - (R) ** 2);
      case Soluce.Genre  is
         when Double =>
            T := Long_Float'Min (Soluce.X1, Soluce.X2);
         when Simple =>
            T := Soluce.X1;
         when Aucune =>
            T := Long_Float'Last;
      end case;
      return T;
   end Time_Back;

   procedure Step (Scene : in out Scene_Type; Step : Long_Float; Nb_Collision : out Integer) is
      Imp            : T_Impact;
      Liste          : Impacts_Liste_Type;
      Dt             : Long_Float := Step; -- pas en temps
      Obj1, Obj2     : Bille_Type;
      Change         : Boolean := False;
      --T: Long_Float;
   begin
      for I in 1 .. Scene.Get_Nb_Objets loop
         Obj1 := Scene.get_objet (I);
         Obj1.Acceleration := Scene.Get_Gravite;
         Scene.Remplacer_Objet (I, Obj1);
      end loop;

      Appliquer_Acceleration (Scene, Dt);

      Update_Pos (Scene, Dt);


      for I in 1 .. Scene.Get_Nb_Objets loop
         Obj1 := Scene.get_objet (I);

         -- mur droite et gauche
         if Obj1.Pos.X >= Get_Width (Scene)- Obj1.Rayon and then Obj1.Vitesse.X > 0.0 then
            Obj1.Vitesse.X := - Amortissement_Mur * Obj1.Vitesse.X;
            Obj1.Pos.X := Get_Width (Scene)- Obj1.Rayon;
            Obj1.Vitesse.Y := Friction_Mur * Obj1.Vitesse.Y;
            Obj1.Acceleration.X := 0.0;
            Change := True;
         elsif Obj1.Pos.X <= 0.0 + Obj1.Rayon and then Obj1.Vitesse.X < 0.0 then
            Obj1.Pos.X := Obj1.Rayon;
            Obj1.Vitesse.X := - Amortissement_Mur * Obj1.Vitesse.X;
            Obj1.Vitesse.Y := Friction_Mur * Obj1.Vitesse.Y;
            Obj1.Acceleration.X := 0.0;
            Change := True;
         end if;

         -- mur haut et bas
         if Obj1.Pos.Y >= Get_Height (Scene)-Obj1.Rayon and then Obj1.Vitesse.Y > 0.0 then
            Obj1.Vitesse.Y := - Amortissement_Mur * Obj1.Vitesse.Y;
            Obj1.Acceleration.Y := 0.0;
            Obj1.Pos.Y := Get_Height (Scene)-Obj1.Rayon;
            Obj1.Vitesse.X := Friction_Mur * Obj1.Vitesse.X;
            Change := True;
         elsif Obj1.Pos.Y <= 0.0 + Obj1.Rayon and then Obj1.Vitesse.Y < 0.0 then
            Obj1.Acceleration.Y := 0.0;
            Obj1.Vitesse.Y := - Amortissement_Mur * Obj1.Vitesse.Y;
            Obj1.Vitesse.X := Friction_Mur * Obj1.Vitesse.X;
            Obj1.Pos.Y := Obj1.Rayon;
            Change := True;
         end if;

         if Change then
            Change := False;
            Scene.Remplacer_Objet (I, Obj1);
         end if;

      end loop;

      --Update_Pos(Scene,Dt);

      for I in 1 .. Scene.Get_Nb_Objets loop
         Obj1 := Scene.get_objet (I);
         for J in I + 1 .. Scene.Get_Nb_Objets loop
            Obj2 := Scene.get_objet (J);
            if Distance_Carre (Obj1, Obj2) < (Obj1.Rayon + Obj2.Rayon) ** 2 and then Scalaire ((Obj2.Vitesse - Obj1.Vitesse), (Obj2.Pos - Obj1.Pos)) < 0.0 then
               --                    T := Time_Back (Obj1, Obj2);
               --                    if abs (T) < Dt then
               --                       Obj1.Pos := Obj1.Pos + Obj1.Vitesse * T;
               --                       Obj2.Pos := Obj2.Pos + Obj2.Vitesse * T;
               --                    end if;

               Choc (Obj1, Obj2);
               Scene.Remplacer_Objet (I, Obj1);
               Scene.Remplacer_Objet (J, Obj2);
            end if;
         end loop;
      end loop;


      Nb_Collision := 0;
   end Step;

end Moteur_Physique;

